// scaup - Scala up!
// (c) 2008-2010, Normen Müller, normen.mueller@gmail.com
// Original version by Jeremy Cloud (http://github.com/jjmmcc/scala0.cmdline#)
// $Id: Argument.scala 48 2010-01-13 08:02:13Z normen.mueller $
package scaup.draft.cmdln

import scala.collection.mutable.ListBuffer

sealed trait Argument

sealed trait OptionSpec {
  def ? = OptionalOptionSpec(this)  
  def |(right: OptionSpec) = OneOfOptionSpec(List(this, right))  
  def ~(right: OptionSpec) = AllOfOptionSpec(List(this, right))  
  def isOptional = false
}

case object NoOptions extends OptionSpec {
  override def ? = error("cannot make NoOptions optional")  
  override def |(right: OptionSpec) = error("cannot mix NoOptions")  
  override def ~(right: OptionSpec) = error("cannot mix NoOptions")  
  override def toString = ""
}

case class OptionalOptionSpec(base: OptionSpec) extends OptionSpec {
  override def toString = "[" + base + "]"  
  override def isOptional = true
}

case class AllOfOptionSpec(elts: List[OptionSpec]) extends OptionSpec {
  override def ~(right: OptionSpec) = AllOfOptionSpec(elts ++ List(right))  
  override def toString = elts.mkString(" ")  
  override def isOptional = elts.forall(_.isOptional)
}

case class OneOfOptionSpec(elts: List[OptionSpec]) extends OptionSpec {
  elts.find(_.isOptional).map(o => throw new IllegalArgumentException("OneOf cannot contain optional alternative"))  
  override def |(right: OptionSpec) = OneOfOptionSpec(elts ++ List(right))  
  override def toString = elts.mkString("(" , " | ", ")")
}

trait CLOption extends Argument with OptionSpec {
  def shortName: Option[String]
  def longName: Option[String]
  def description: String
  def expectsValue: Boolean
  /** Returns a mildly-formatted string containing the short and long names, and the description */
  def describe: String    
  //@todo break up line into 80 char units
  private[cmdln] def makeDescription(description: String, usage: String*) = "\t" + usage.mkString(", ") + "\n\t\t" + description
}

object CLOption {
  object LongForm {
    def unapply(arg: String): Option[(String, Option[String])] =
      if (arg startsWith "--") 
        arg.indexOf("=") match {
          case -1 => Some((arg, None))
          case eq => Some((arg.substring(0, eq), Some(arg.substring(eq + 1))))          
        }
      else None        
  }
    
  object ShortForm {
    def unapply(arg: String): Option[String] = if (arg startsWith "-") Some(arg) else None
  }
}

case class Flag(shortName: Option[String], longName: Option[String], description: String) extends CLOption {
  if (!shortName.isDefined && !longName.isDefined) error("Flag must have at least one of shortName or longName")  
  def expectsValue = false  
  override def toString = if (shortName.isDefined) "-" + shortName.get else "--" + longName.get
  def describe = {
    val usage = new ListBuffer[String]
    if (shortName.isDefined)
      usage += "-" + shortName.get
    if (longName.isDefined)
      usage += "--" + longName.get
    makeDescription(description, usage: _*)
  }
}

trait OptionWithValue extends CLOption {
  def expectsValue = true
  def argName: String
  def checkValue(value: String): Option[ValidationFailure]
  override def toString = if (shortName.isDefined) "-" + shortName.get + " " + argName else "--" + longName.get + "=" + argName
  def toString(value: String) = if (shortName.isDefined) "-" + shortName.get + " " + value else "--" + longName.get + "=" + value
  def describe = {
    val usage = new ListBuffer[String]
    if (shortName.isDefined)
      usage += "-" + shortName.get + " " + argName
    if (longName.isDefined)
      usage += "--" + longName.get + "=" + argName
    makeDescription(description, usage: _*)
  }
}

case class StringOption(shortName: Option[String], longName: Option[String], argName: String, description: String) extends OptionWithValue {
  if (!shortName.isDefined && !longName.isDefined) error("StringOption must have at least one of shortName or longName")  
  def checkValue(value: String): Option[ValidationFailure] = None
}

case class IntOption(shortName: Option[String], longName: Option[String], argName: String, description: String) extends OptionWithValue {
  if (!shortName.isDefined && !longName.isDefined) error("IntOption must have at least one of shortName or longName")
  def checkValue(value: String): Option[ValidationFailure] =
    try {
      value.toInt
      None
    } catch {
      case ex: NumberFormatException => Some(NotAnInteger(this, value))
    }
}

object NakedArgument {
  def apply(name: String, description: String) = new NakedArgument(name, true, false, description)
}

case class NakedArgument(name: String, required: Boolean, repeated: Boolean, description: String) extends Argument {
  def ? = NakedArgument(name, false, repeated, description)    
  def * = NakedArgument(name, false, true, description)  
  def + = NakedArgument(name, true, true, description)   
  override def toString = {
    val prefix = if (required) name else "[" + name + "]"
    val suffix = if (repeated) "..." else ""
    prefix + suffix
  }
}
